from django.db import models
from django.utils.functional import curry
from django.db.models.fields import *
from nokia.tt.models import *
import datetime
import string

def get_model_from_verbose_name(v_name):
    """Returns the class of a model with _meta.verbose_name = v_name"""
    apps = [a.split('.')[-1] for a in settings.INSTALLED_APPS]
    m_name = ''.join(v_name.split())
    for a in apps:
        m = models.get_model(a,m_name)
        if m:
            return m

class InvalidFunctionError(Exception):
	def __init__(self, func, obj):
	    self.func = func
	    self.obj = obj
	def __str__(self):
	    return "Calling %s with %s as an arguement resulted in an error" \
                            % (self.func.__name__, self.obj)

class FieldAlreadyExistsError(Exception):
    def __init__(self, class_reg,  field):
        self.model = class_reg
        self.field = field
    def __str__(self):
        return "%s already exists on %s" % (self.field, self.model)
        
class NoSuchFieldError(Exception):
    def __init__(self, class_reg, field):
        self.class_reg = class_reg
        self.field = field
    def __str__(self):
        return "%s does not have field '%s'" % (self.class_reg, self.field)
        

#Testing only, this should belong in each module
FK_LOOKUPS = {
    Status: 'name',
    Priority: 'name',
    User: 'username',
    Group: 'name',
    'default': 'id',
    }

def prettify_string(s):
    """For strings with and underscore, replace it with a space and capitalize
the first word"""
    return string.capwords(s.replace('_',' '))

# FUNTIONS for ReporterField
def get_field(fld_name,obj):
    """Returns a value for obj.fld_name, traversing FK relationships.
    Used to curry a funtion in ClassRegister objects"""
    field = obj._meta.get_field(fld_name)
    field_value = getattr(obj,fld_name)
    if field.__class__.__name__ == 'ForeignKey':
        try:
            new_field = FK_LOOKUPS.get(field.rel.to,'id')
            field_value = get_field(new_field,field_value)
        except:
            field_value = None
    #Handle datetime and timedeltas... slightly hackish!
    return field_value

def get_property(prop_name, obj):
	"""Simple property field"""
	return getattr(obj,prop_name)

def get_m2m_count(fld_name, obj):
    """Count of many_to_many field"""
    return int(getattr(obj, fld_name).count())

def get_relation_count(related_object, obj):
    """Return a count of the related_objects refering to obj"""
    if not related_object.get_accessor_name(): #It's a self referential m2m
    	return int(getattr(obj, related_object.field.name).count())
    else:
        return int(getattr(obj, related_object.get_accessor_name()).count())

# CLASSES
class ReporterField(object):
    def __init__(self, title, function, model, field, type=None):
        """Title is for the header row, function takes one arguement, an object
of type queryset.model and returns a value to use in the report, type is a 
string describing the type of ReporterField"""
	self.title = title
	self.function = function
	self.model = model
	self.field = field
	self.type = type

    def __str__(self):
        return "%s: %s.%s" % (self.title, self.model,self.field)

    def __repr__(self):
        return "<ReporterField: %s.%s>" % (self.model,self.field)

    def __call__(self, obj):
        """Calculate the field on obj using self.function"""
        try:
            return self.function(obj)
        except Exception, e:
            print e
            raise
	    #raise InvalidFunctionError(self.function, obj)

class ClassRegister(object):
    """Class to store and enable lookup of ReporterField"""
    def __init__(self,cls):
    	self.dj_model = cls
        try: del cls._meta._all_related_objects
        except: pass 
        try: del cls._meta._all_related_many_to_many_objects	
        except: pass
        self._fields = cls._meta.fields
        self._m2ms = cls._meta.many_to_many
        self._properties = [k for k,v in cls.__dict__.items() if v.__class__ == property]
        related_fks = cls._meta.get_all_related_objects()
        related_m2ms = cls._meta.get_all_related_many_to_many_objects()
        self._rev_related = related_fks + related_m2ms #These are all the same object type
        self.report_field_filter = lambda fld_type, rep_fld: fld_type == rep_fld.type
        self._registered_report_flds = []
        self._get_standard_report_flds()

    def __repr__(self):
    	return "<ClassRegister for %s>" % self.dj_model
    	
    def _get_standard_report_flds(self):
    	"""Build the list of all of the standard report fields. i.e all of a
    	model's fields, properties and related object counts"""
    	self._db_flds = []
        self._property_flds = []
        self._counts = []
        mod_name = self.dj_model.__name__
    	for f in self._fields:
            name = f.name
            type = f.__class__.__name__
            func = curry(get_field,name)
            func.__doc__ = """Value of field "%s". (type: %s)""" % (name, type)
            rf = ReporterField(f.verbose_name, func, mod_name, name, type)
            self.__dict__[name] = rf
            self._db_flds.append(rf)
        for f in self._m2ms:
            name = f.name
            title = 'Count of %s'  % prettify_string(name)
            type = f.__class__.__name__
            func = curry(get_m2m_count,name)
            func.__doc__ = """Count of ManyToManyField "%s".""" % name
            rf = ReporterField(title, func, mod_name, '%s.count()' % name, type)
            self.__dict__[name] = rf
            self._db_flds.append(rf)
    	for p in self._properties:
            func = curry(get_property,p)
            title = prettify_string(p)
            func.__doc__ = """Value of property "%s".""" % p
            rf = ReporterField(title, func, mod_name,p,'property')
            self.__dict__[p] = rf
            self._property_flds.append(rf)
    	for ro in self._rev_related:
    		#the or is for the case of a self referential m2m
            name = ro.get_accessor_name() or ro.field.name
            title = "Count of %s" % prettify_string(name)
            func = curry(get_relation_count,ro)
            func.__doc__ = """Count of Reverse Relationship "%s".""" % name
            rf = ReporterField(title, func, mod_name, '%s.count()' % name, 'int')
            self.__dict__[name] = rf
            self._counts.append(rf)
    def _get_fields_from_report_module(self):
        """Try to import self.dj_model.__module__.reports. This is the default place
to define reporting functions, all of the functions in there that are
decorated with FieldRegister.register will be added automatically. You
must also have reg = FieldRegister() in this module"""
        try:
            reg =  __import__(self.dj_model.__module__.replace('models', 'reports'), \
                                    fromlist=['*']) .reg
            
            #print reg.Ticket.__dict__.keys()
            #functs = [v for v in mod.__dict__.values() if type(v) == types.FunctionType]
        except ImportError,  e:
            pass #If there's no module we don't care
            
        
    
    def _get_field_type(self,type):
    	filter_func = curry(self.report_field_filter,type)
    	return filter(filter_func,self._db_flds)
    
    @property
    def counts(self):
        return self._counts
        
    @property
    def properties(self):
        return self._properties
        
    @property
    def user_defined(self):
        return self._registered_report_flds
    
    def __getattr__(self,key):
        """Some conviniences for getting different field types"""
        _splt = key.split('_')
        if len(_splt) == 2 and _splt[1] == 'fields':
    		if _splt[0] == 'm2m':
    			val = self._get_field_type('ManyToManyField')
    			self.__dict__['m2m_fields'] = val
    			return val
    		elif _splt[0] == 'fk':
    			val = self._get_field_type('ForeignKey')
    			self.__dict__['fk_fields'] = val
    			return val
        raise AttributeError,  "%s does not have attribute %s" % (self, key)

	
class FieldRegister(object):
    """A registry of ReporterFields for models. Is a Borg, i.e one state for
    multiple instances. When get_for_model is called __shared_state will be
    a dictionary of model --> 'fields' --> all of the object's fields
                          --> 'counts' --> all of the objects related sets"""
    __shared_state = {}
    def __init__(self):
    	self.__dict__ = self.__shared_state
		
    def __getattr__(self,key):
    	model = get_model_from_verbose_name(key)
    	if model:
            try:
                class_reg = self.__dict__[model.__name__]
            except KeyError:
                class_reg = ClassRegister(model)
                
                self.__dict__[model.__name__] = class_reg
            class_reg._get_fields_from_report_module()
            return class_reg
    	raise AttributeError,  "%s does not have attribute %s" % (self, key)
    
    def register(self, *args):
        """Decorator to register a function. Constructs a ReporterField and adds
        it to the ClassRegister for model's _registered_fields list"""
        def decorate(func):
            """Creates a ReporterField,  registers it and returns the function
            unchanged"""
            title,  model,  field = args
            class_reg = getattr(self,  model) #model should be model.__name__
            rf = ReporterField(title, func,  model,  field, type='user')
            
            if class_reg.__dict__.has_key(field):
                raise FieldAlreadyExistsError(model,  field)
            class_reg._registered_report_flds.append(rf)
            class_reg.__dict__[field] = rf
            return func
        return decorate
        
class Reporter(object):
    """Assembles the given fields,  and when called with a QuerySet returns an
    iterator of a list of the fields applied to each object in the qs"""
    
    def __init__(self,  model,  register,  *fields):
        """fields is a tuple of strings and each must be present in the
        ClassRegister for this model"""
        self.model = model
        self.register = register
        self.fields = self.check_fields(fields)
        
    def __call__(self,  queryset, header=True):
        assert queryset.model == self.model,  \
        "This report is for objects of type %s but the QuerySet it was called with it of type %s"\
        % (self.model, queryset.model)
        if header:
            yield [f.title for f in self.fields ]
        for obj in queryset.iterator():
            yield [format_row_item(f(obj)) for f in self.fields]
        
    def check_fields(self,  fields):
        class_reg = getattr(self.register, self.model.__name__)
        checked_fields = []
        for f in fields:
            try:
                checked_fields.append(getattr(class_reg, f))
            except AttributeError,  e:
                print 'Invalid field'
                raise NoSuchFieldError(class_reg, f)
                
        return [getattr(class_reg, f) for f in fields]

def format_row_item(item):
    format_dict = {
        datetime.datetime: lambda time: time.strftime("%Y-%m-%d %H:%M"),
        datetime.timedelta: lambda td: td.seconds/3600.0
        }
    fmt = format_dict.get(item.__class__, None)
    if fmt:
        return fmt(item)
    return item
            
